"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" (C)Copyleft, Sunding Wei (Alex), May 29, 2006, GZ, China
"             Email:swei@arrayinc.com
"
"        ArrAy WAD VIM Plugin
"               
"        $Header: /home/array/cvs/ArrAy/toolkit/VSECommon/macros/wad.vim,v 1.32 2007/06/07 04:32:07 swei Exp $
"        $Author: swei $
"        $Date: 2007/06/07 04:32:07 $
"        $Revision: 1.32 $
"
"
" Installation:
"   
"       1) Copy "wad.vim" to your VIM plugin directory
"
"         - for Linux/Unix: /usr/share/vim/vim??/plugin
"         - for WIN32 GVIM: C:\Program Files\Vim\vim??\plugin
"
"       2) Change the default Author
"         
"          Add following line    
"           
"                let g:array_user = "Your Name Here"
"           to:
"           WIN32 gVIM: C:\Program Files\Vim\_vimrc
"           Linux/Unix: ~/.vimrc
"
"           It's highly recommended to set your ID by this way, once this
"           plugin is updated, you needn't to modify your ID again.
"
"       3)  Disable entering "Insert Mode" after activating the macro:
"           Add following line to .vimrc(Linux) / _vimrc(WIN) file
"
"                let g:insert_mode = 0
"
" Usage:
"      1) Entering macros
"         
"         - For "Normal Mode" (Vim / gVim)
"           ``````````````````````````````
"           Press ESC anytime will enter the "Normal Mode", type the macros
"           defined by ArrAy directly, for example: mjournal. NOTE, you need
"           not to type Enter extra.
"
"         - For "Insert Mode" ( Currently works only in gVim)
"           `````````````````````````````````````````````````
"           you can do what you did in SlickEdit, type the macros in insert
"           mode then press Ctrl+Enter, for example:
"
"               mjournal <Ctrl+Enter>
"
"      2) Selecting options 
"
"         - Using function key F2
"
"           F2 can be used to select options under both "Normal Mode" and 
"           "Insert Mode".
"
"            I)  Place the cursor within the double quotations, like below:
"                (The "|" stands for the cursor)
"              
"                TeamPriority = "M|edium" 
"
"            II) Then press F2 continously, select the option you want.
"
"            Note: Popup menu works only under Vim7/GVim7 or higher version.
"
"      3) Tips
"
"         - F2 can also be used to updte "EndTimeStamp". :-)
"         - Entering "Normal Mode": Press ESC anytime
"         - Entering "Insert Mode": Press "i" key under Normal-Mode.
"
"        
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

if exists("ArrAy_WAD_Plugin")
    finish
endif
let ArrAy_WAD_Plugin = 1

" The default author ID
" this can be set in .vimrc: let g:array_user = "your_id" 

let s:author    = "zgao"

" InsertMode is for newbie to vim, after typed the macro, VIM will enter the 
" Insert Mode, otherwise, it'll stay on the Normal Mode.
" this can be disabled in .vimrc: let g:insert_mode = 0

let s:insert_mode  = 1 

" Set the timeout for typing macros
" the Unit is millisecond

set timeout
set ttimeout
set timeoutlen=3000
set ttimeoutlen=3000

" Show incomplete command
set showcmd

" Maximize the window for gVIM
if has("gui_running")
    let os = (stridx($VIM, ":\\") > -1) ? "WINDOWS" : "UNIX"
    if os == "WINDOWS"
        au GUIEnter * simalt ~x
        set guifont=Courier_New:h10:cANSI 
    elseif os == "UNIX"
        set guifont=Bitstream\ Vera\ Sans\ Mono\ 11
    endif
endif

" We dont't think backup file is needed.
set nobackup

" Check the filetype, only empty file and XML file are allowed.
"if argv(0) != ""
"    if argv(0) !~? ".*\.xml"
"        "Not a XML file
"        unlet ArrAy_WAD_Plugin
"        finish
"    endif
"endif

if exists("g:array_user")
    let s:author = g:array_user
endif

if exists("g:insert_mode")
    let s:insert_mode = g:insert_mode 
endif

" Set textwidth=80 etc.
autocmd FileType xml    call XmlFileDetected()

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"
" Options and other variables
"
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

" Current Line Number
let s:current     = 1 
let g:originalMode= 1
" Constant
let g:INSERT_MODE = 0
let g:NORMAL_MODE = 1

let s:tips = "Don't forget to use F2 to scroll options! :-)"
let s:separator = "<!--=========================================================================-->"
let s:journalCategoris = ":Bug:Consulting:Design:Enhancement:Level2Support:Ports:ReleaseManagement:RTOK:SQAConsulting:SQAPlanning:Team:TOK:High:"
let s:journalComplexities = ":High:Medium:Low:Unknown:"
let s:journalTeamPriorities = ":Critical:High:Medium:Low:"
let s:closeReasons = ":AlreadyFixed:Completed:Duplicate:NotABug:Reassigned:Terminated:Unreproducible:Withdrawn:"
let s:stages = ":Triage:Work:Test:Audit:Delivery:"
let s:states = ":Active:Suspended:On-hold:Closed:"
let s:purposes = ":Assumptions:Checkpoint:Discovery:Issues:News:Plan:Progress:Review:"
let s:infoTypes = ":HowTo:Doc:Client:General:"

" 
" Macros list
" mendtime, mdate, mtime, mtimespent -- are special insert commands 
"
let s:macros = "mauditor,mclose,mclosure,mcode,mcrew,mcustom,mdisp,memail," 
let s:macros = s:macros . "mentry,mfile,mhypo,minfo,minter,mjournal,mnext,mrepro,mresult," 
let s:macros = s:macros . "mstrat,msummary,mtext,mwrap"

" Items have dropdown list (Popup menu)
let s:droplist = "Category,Complexity,TeamPriority,ClientPriority,ClosedReason,State,Stage,Purpose,InfoType,"

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"
" Common functions
"
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

function GetDays(year, month)

    let y = a:year

    if a:month == 1
        return 31
    elseif a:month == 2
        return 28 + ((y % 4 == 0 && y % 100 !=0) || y % 400 == 0)
    elseif a:month == 3
        return 31
    elseif a:month == 4
        return 30
    elseif a:month == 5
        return 31
    elseif a:month == 6
        return 30
    elseif a:month == 7
        return 31
    elseif a:month == 8
        return 31
    elseif a:month == 9
        return 30
    elseif a:month == 10
        return 31
    elseif a:month == 11
        return 30
    elseif a:month == 12
        return 31
    endif
        
endfunction


" Get relative seconds from Base Year 
" (c) Copyrights, sunding wei
function GetSecondFromBase(baseYear, year, month, day, hour, minute, second)

    let y = a:baseYear
    let days = 0
    while y <= a:year
        let m = 1
        let d = 12
        if y == a:year
            " Prev month
            let d = a:month - 1
        endif
        while m <= d
            let days = days + GetDays(y, m) 
            let m = m + 1
        endwhile
        let y = y + 1
    endwhile

    " add to lastday
    let days = days + a:day - 1
    let total = days * 24 * 3600 + a:hour * 3600 + a:minute * 60 + a:second

    return total
endfunction

"
" @date1 - @date2
" 2006-06-08 14:39:38 - 2006-06-08 14:39:44
" @return seconds 
"
function DateDiff(date1, date2)
   
   if strlen(a:date1) != 19 || strlen(a:date2) != 19
      return 0
  endif     

   let year1   = 0 + strpart(a:date1, 0, 4)
   let month1  = 0 + strpart(a:date1, 5, 2)
   let day1    = 0 + strpart(a:date1, 8, 2)
   let hour1   = 0 + strpart(a:date1, 11, 2)
   let minute1 = 0 + strpart(a:date1, 14, 2)
   let second1 = 0 + strpart(a:date1, 17, 2) 

   let year2   = 0 + strpart(a:date2, 0, 4)
   let month2  = 0 + strpart(a:date2, 5, 2)
   let day2    = 0 + strpart(a:date2, 8, 2)
   let hour2   = 0 + strpart(a:date2, 11, 2)
   let minute2 = 0 + strpart(a:date2, 14, 2)
   let second2 = 0 + strpart(a:date2, 17, 2) 

   let base = year1
   if base > year2
       let base = year2
   endif 

   " Get days relative to base year
   let total1 = GetSecondFromBase(base, year1, month1, day1, hour1, minute1, second1)
   let total2 = GetSecondFromBase(base, year2, month2, day2, hour2, minute2, second2)
   let diff   = total1 - total2

   if diff < 0
       let diff = -diff
   endif

   return diff

endfunction


function NCompare(str1, str2, n)

    let len = strlen(a:str1)
    if len > strlen(a:str2)
        let len = strlen(a:str2)
    endif
    if len > a:n
        let len = a:n
    endif

    let i = 0
    while i < len 
       if a:str1[i] != a:str2[i]
           return 0
       endif
       let i = i + 1
    endwhile

    return 1

endfunction

" To check if current file is a WAD.
function IsWAD()
    let i = 1
    let endline = line("$")
    if endline > 20 
        " only check the first 20 lines.
        let endline = 20 
    endif
    let keyPoints = 0
    while i <= endline
        let str = getline(i)
        if stridx(str, "DOCTYPE") > -1  
            let keyPoints = keyPoints + 1
        endif
        if stridx(str, "ArrAy.dtd") > -1
            let keyPoints = keyPoints + 1
        endif
        if stridx(str, "xml-stylesheet") > -1
            let keyPoints = keyPoints + 1
        endif
        if stridx(str, "ArrAy.xsl") > -1
            let keyPoints = keyPoints + 1
        endif
        
        if keyPoints >= 4 
            break
        endif

        let i = i + 1
    endwhile

    return keyPoints >= 4
endfunction

" Xml file type detected
function XmlFileDetected()

    if IsWAD() 
        " We'll disable the XmlIndent if current file is a WAD
        set indentexpr=""
        set autoindent
        set textwidth=80
    endif

endfunction

" Pad 0s to left
function PadLeft(val, count)
    let s = "" . a:val
    let i = a:count - strlen(s)
    while i > 0
        let s = "0" . s
        let i = i - 1
    endwhile
    return s    
endfunction

function EchoError(msg)
    echohl ErrorMsg
    echo a:msg
    echohl None
endfunction

function EchoHighlight(msg)
    echohl Search
    echo a:msg
    echohl None
endfunction

function Now()
    return strftime("%Y-%m-%d %H:%M:%S")
endfunction

" Start inserting the XML
function AppendFirst(content)

    let tmp = substitute(a:content, "<mtime>", Now(), "")
    let tmp = substitute(tmp, "<timestamp>", Now(), "")
    let tmp = substitute(tmp, "<mauthor>", s:author, "")

    if IsBlankLine(s:current)
        call setline(s:current, tmp)
    else
        call append(s:current, tmp) 
        let s:current = s:current + 1
    endif

endfunction

function AppendLine(content)

    let tmp = substitute(a:content, "<mtime>", Now(), "")
    let tmp = substitute(tmp, "<timestamp>", Now(), "")
    let tmp = substitute(tmp, "<mauthor>", s:author, "")

    call append(s:current, tmp) 
    let s:current = s:current + 1

endfunction

function IsBlankLine(l)
    let ln = a:l
    if a:l == "."
        let ln = line(".")
    endif

    return nextnonblank(ln) != ln

endfunction

" Use F2 to scroll the options
function ScrollOption()

    let retVal = ""
    let currentRow = line(".")
    let currentCol = col(".")

    if !IsCorrectPos(getline(currentRow), currentCol)
        call EchoError("Please place cursor within the double quotations!")
        return retVal
    endif

    " find XML Tag backward
    let curline = getline(currentRow) 

    let attrName =  GetAttributeName(curline, currentCol)
    let value = GetAttributeValue(curline, attrName)
   
    if attrName == "Category"
        let value = GetNextOption(s:journalCategoris, value)
    elseif attrName == "Complexity"
        let value = GetNextOption(s:journalComplexities, value)
    elseif attrName == "TeamPriority"
        let value = GetNextOption(s:journalTeamPriorities, value)
    elseif attrName == "ClosedReason"
        let value = GetNextOption(s:closeReasons, value)
    elseif attrName == "State"
        let value = GetNextOption(s:states, value)
    elseif attrName == "Stage"
        let value = GetNextOption(s:stages, value)
    elseif attrName == "Purpose"
        let value = GetNextOption(s:purposes, value)
    elseif attrName == "InfoType"
        let value = GetNextOption(s:infoTypes, value)
    else
        return retVal
    endif

    let newLine = SetAttributeValue(curline, attrName, value)
    call setline(currentRow, newLine)

    return retVal
endfunction


" 
" To be compatible with 6.4 or lower version
" this function like inputlist() in 7.0.
" @msg  - the prompt message
" @ops  - options for user to select, separated by ":"
"
function InputOption(msg, ops, default)
   
    let val = ""
    let tmp = ""
    let i = 0
    let item = 1 
    let len = strlen(a:ops)
    
    while i < len 
        "check only if is not the last ':'
        if a:ops[i] == ":" && i < len - 1 
            if tmp != ""
                let tmp = tmp . "\n"
            endif
            let tmp = tmp . item . "."
            let item = item + 1
        else
            let tmp = tmp . a:ops[i]
        endif   
        let i = i + 1
    endwhile

    let tmp = a:msg . "\n" . tmp . "\nType number: "

    " Always asks for input
    while 1
        let val = Trim(input(tmp, a:default))
        if  val == "" || IsNumber(val) 
            break
        else
            call EchoError("Invalid input, number only!")
        endif
    endwhile

    if val == ""
        let val = a:default
    endif

    " Get the corresponding option
    let index = 0 + val 
    let i = 0
    let t = ""
    while i < strlen(a:ops)
        if a:ops[i] == ":"
            let index = index - 1
        endif
        " found
        if index == 0
            let i = i + 1
            while a:ops[i] != ":" && i < strlen(a:ops)
                let t = t . a:ops[i]
                let i = i + 1
            endwhile
            break
        endif
        let i = i + 1
    endwhile

    return t

endfunction


" In convenient of searching the options
" all options was enclosed by ":"
" example: 
"  
"   let options = ':A:B:C:D:'
"   
function GetNextOption(options, currentOption)

    let part = ":" . a:currentOption . ":"
    let partlen = strlen(part)
    let index = stridx(a:options, part)

    if index < 0
        " not found
        let index = 1
        let partlen = 0
    elseif index + partlen == strlen(a:options)
        " last one
        let index = 1
        let partlen = 0
    endif

    let s:next = ""
    let t = index + partlen 
    while t < strlen(a:options)
        let char = strpart(a:options, t, 1)
        if char == ":"
            break
        endif
        let s:next = s:next . char
        let t = t + 1   
    endwhile

    return s:next

endfunction

" find the Attrbute Name of current cursor
function GetAttributeName(str, pos)

    let attr = ""
    let begin = 0
    let t = a:pos - 1

    while t >= 0

        let char = strpart(a:str, t, 1)
        if !begin && (char == "=")
            let begin = 1           
            " Skip spaces
            let t = t - 1
            while t >= 0
                let char = strpart(a:str, t, 1)
                if char =~ "[a-zA-Z]"
                    break
                endif
                let t = t - 1
            endwhile                
        endif

        if begin 
            if char !~ "[a-zA-Z]"
                break
            endif
            let attr = char . attr 
        endif

        let t = t - 1

    endwhile

    return attr

endfunction

" find the Attribute Value of specific Attrbute name on current line
function GetAttributeValue(str, attrName)
    let value = ""
    let index = stridx(a:str, a:attrName)
    if index < 0
        return ""
    endif

    let t = index + strlen(a:attrName) + 1
    let begin = 0
    while t < strlen(a:str)

        let char = strpart(a:str, t, 1)
        if !begin && (char == '"')
            let begin = 1
            let t = t + 1
            let char = strpart(a:str, t, 1)
        endif   

        if begin
            if char == '"'
                break
            endif       
            let value = value . char
        endif
        let t = t + 1       

    endwhile    

    return value

endfunction

function SetAttributeValue(str, attrName, value)

    let index = stridx(a:str, a:attrName)
    if index < 0
        return a:str
    endif

    " Catch the attribute name
    let s:count = 0
    let t = 0
    while t < strlen(a:str)
        let char = strpart(a:str, t, 1)
        if char == '"'
            let s:count = s:count + 1
            let t = t + 1
            continue
        endif

        " We can only find the Attribute Name out of double quotations.
        if s:count % 2 == 0
            if StartsWith(a:str, t, a:attrName)
                " Attribute Name found.
                " now, find the "=" sign
                let t = t + strlen(a:attrName)
                while t < strlen(a:str)
                    if a:str[t] == "="
                        break
                    endif
                    let t = t + 1
                endwhile

                break       
            endif
        endif

        let t = t + 1
    endwhile    

    " We must found the Attribute Name here, and at the 
    " positon of the "=" sign.
    " now, continue to find the first '"'
    let t = t + 1
    while t < strlen(a:str)
        if a:str[t] == '"'
            break
        endif
        let t = t + 1
    endwhile

    " strip the left part without the first '"'
    let s = strpart(a:str, 0, t)

    " Append the new value
    let s = s . '"' . a:value . '"'

    " find the right part, the position of second quotation
    let t = t + 1
    while t < strlen(a:str)
        if a:str[t] == '"'
            break
        endif
        let t = t + 1
    endwhile

    " Now we can get the right part without the last '"'
    let s = s . strpart(a:str, t + 1)   

    return s

endfunction

" if str starts with keyword
function StartsWith(str, startPos, keyword)

    if strlen(a:str) < strlen(a:keyword)
        return 0
    endif

    if a:startPos > strlen(a:str) - 1
        return 0
    endif

    let t1 = a:startPos 
    let t2 = 0
    let matched = 1
    while t2 < strlen(a:keyword)
        if strpart(a:str, t1, 1) != strpart(a:keyword, t2, 1)
            let matched = 0
            break
        endif       
        let t1 = t1 + 1
        let t2 = t2 + 1
    endwhile

    return matched

endfunction

" To check if the current cursor is within the double quotation.
function IsCorrectPos(str, column)
    let t1 = 0
    let t2 = a:column - 1
    " cout of quotation
    let c1 = 0
    let c2 = 0

    while t1 < a:column - 1
        if strpart(a:str, t1, 1) == '"'
            let c1 = c1 + 1
        endif
        let t1 = t1 + 1
    endwhile

    while t2 < strlen(a:str)
        if strpart(a:str, t2, 1) == '"'
            let c2 = c2 + 1
        endif
        let t2 = t2 + 1
    endwhile

    let mod1 = c1 % 2
    let mod2 = c2 % 2

    return (mod1 == mod2) && (mod1 == 1)

endfunction


"Get next ID of Hypo, Strat, Closure
function GetNextID(tagName, attrName, prefix)
    let lastLine = line("$")
    let t = 1
    let s:id = 0
    while t <= lastLine
        let s = getline(t)
        if stridx(s, "<" . a:tagName) >= 0 
            " found the tag, and get the integer value of it
            let value = GetAttributeValue(s, a:attrName)
            let value = 0 + strpart(value, 1) 
            if value > s:id
                let s:id = value
            endif
        endif
        let t = t + 1
    endwhile

    let s:id = s:id + 1
    return a:prefix . s:id

endfunction


" Check if current line or previous line is separator
function SeparatorExists(lineNum)

    let lNum = a:lineNum
    if lNum == "."
        let lNum = line(".")
    endif

    if lNum < 2
        return 0
    endif

    let curLine = Trim(getline(lNum))
    let preLine = Trim(getline(lNum - 1))

    " if current line is blank and previous line is separator
    " we needn't to insert the head separator
    if IsBlankLine(lNum) 
        return NCompare(preLine, s:separator, 72)
    endif

    " In compatible with Emacs-plugin's separator, there's only 79 chars.
    return NCompare(curLine, s:separator, 72)

endfunction


""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"
" Xml Tags  
"
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

function InsertAuditor()
    let s:current = line(".")
    call AppendFirst('<Auditor Name = "<mauthor>" TimeStamp = "<mtime>"/>')
    call cursor(s:current, 0)
endfunction

function InsertClose()
    let s:current = line(".")
    call AppendFirst('<ClosedEvent')
    call AppendLine(' Authors = "<mauthor>"') 
    call AppendLine(' Stage = "Delivery"') 
    call AppendLine(' State = "Closed"')  
    call AppendLine(' ClosedReason = "Completed"')
    call AppendLine(' EventTimeStamp = "<mtime>"><![CDATA[')
    call AppendLine('')
    call AppendLine(' ]]></ClosedEvent>')
    call cursor(s:current - 1, 0)
    call EchoHighlight(s:tips)
endfunction

function InsertClosure()
    let s:current = line(".")
    let s:sep = SeparatorExists(".")

    if s:sep 
        call AppendFirst('<Closure Revision = "' . GetNextID("Closure", "Revision", "C") . '">')
    else
        call AppendFirst(s:separator)
        call AppendLine('<Closure Revision = "' . GetNextID("Closure", "Revision", "C") . '">')
    endif

    call AppendLine('<ClosureDescription><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></ClosureDescription>')
    call AppendLine('<Analysis><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Analysis>')
    call AppendLine('<Solution><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Solution>')
    call AppendLine('<Documentation><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Documentation>')
    call AppendLine('<Testing>')
    call AppendLine('<Instructions><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Instructions> ')
    call AppendLine('<Before><![CDATA[')
    call AppendLine('<!-- Text describing behavior before the changes have been made -->')
    call AppendLine('')
    call AppendLine(']]></Before>')
    call AppendLine('<After><![CDATA[')
    call AppendLine('<!-- Text describing behavior after the changes have been made -->')
    call AppendLine('')
    call AppendLine(']]></After>')
    call AppendLine('</Testing>')
    call AppendLine('<Deliverables><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Deliverables>')
    call AppendLine('<Diffs>')
    call AppendLine('<!-- (Files)* -->')
    call AppendLine('</Diffs>')
    call AppendLine('<Auditor Name= "<mauthor>" TimeStamp = "<mtime>"/>')
    call AppendLine('<!--may contain multiple Reviewer tags-->')
    call AppendLine('</Closure>')
    call AppendLine(s:separator)

    call cursor(s:current - 33, 0) 
endfunction

function InsertCode()
    let s:current = line(".")
    call AppendFirst('<Code FileName = "" FileRevision = "1.0"><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Code>')
    call cursor(s:current - 2, 19)
endfunction

function InsertCrew()
    let s:current = line(".")
    call AppendFirst('<Crew Names = ""/>')
    call cursor(s:current, 16)
endfunction

function InsertCustom()
    let s:current = line(".")
    call AppendFirst('<Custom CustomType = "">')
    call AppendLine('')
    call AppendLine('</Custom>')
    call cursor(s:current - 2, 23)
endfunction

function Date()
    return strftime("%Y-%m-%d")
endfunction

function InsertDate()
    "execute "normal i" . Date()
    let c   = col(".")
    if c < 1
        let c = 1
    endif
    let str = getline(".")
    let str = strpart(str, 0, c - 1) . Date() . strpart(str, c - 1)
    call setline(".", str)

endfunction

function InsertDisp()
    let s:current = line(".")
    call AppendFirst('<DispatchEvent')
    call AppendLine('  Authors = "<mauthor>" ')
    call AppendLine('  Stage = "Work" ')
    call AppendLine('  State = "Active" ')
    call AppendLine('  EventTimeStamp = "<mtime>">')
    call AppendLine('<!-- (#PCDATA | Crew | Intervention)* -->')
    call AppendLine('')
    call AppendLine('</DispatchEvent>')

    call cursor(s:current - 1, 0)
    call EchoHighlight(s:tips)

endfunction

function InsertEmail()
    let s:current = line(".")
    call AppendFirst('<EMail>')
    call AppendLine('<!-- Copy of email sent or received -->')
    call AppendLine('<![CDATA[')
    call AppendLine('')
    call AppendLine(']]></EMail>')
    call cursor(s:current - 1, 0)
endfunction

function InsertEntry()
    let s:current = line(".")
    let s:sep = SeparatorExists(".")

    if s:sep 
        call AppendFirst('<Entry ')
    else
        call AppendFirst(s:separator)
        call AppendLine('<Entry ')
    endif

    call AppendLine('  Authors = "<mauthor>" ')
    call AppendLine('  StartTimeStamp = "<mtime>" ')
    call AppendLine('  Stage = "Work" ')
    call AppendLine('  Purpose = "Progress" ')
    call AppendLine('  EndTimeStamp = "<mendtime>">')
    call AppendLine('<!--(Text| Info| Hypo| Strat| Summary| Repro| Result| NextSteps| EMail| Code)*-->')
    call AppendLine('<Text><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Text>')
    call AppendLine('</Entry>')

    " to check if next line is separator
    let s:sep = SeparatorExists(s:current + 1)
    let s:offset = 0
    if !s:sep 
        call AppendLine(s:separator)
        let s:offset = 1
    endif

    call cursor(s:current - s:offset - 2, 0)
    call EchoHighlight(s:tips)
    set filetype=xml

endfunction

function InsertFile()
    let s:current = line(".")
    call AppendFirst('<Files FileName = ""><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Files>')
    call cursor(s:current - 2, 20)
endfunction

function InsertHypo()
    let s:current = line(".")
    call AppendFirst('<Hypo ID = "' . GetNextID("Hypo", "ID", "H") .'">')
    call AppendLine('<!-- Detailed description of a working theory -->')
    call AppendLine('<![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Hypo>')
    call cursor(s:current - 1, 0)
endfunction

function InsertInfo()
    let s:current = line(".")
    call AppendFirst('<Info ')
    call AppendLine('  InfoType = "HowTo" ')
    call AppendLine('  InfoSrc = "" ')
    call AppendLine('  KeyWords = "">')
    call AppendLine('<!-- Infomation that we expect to reuse and want a quick way to find -->')
    call AppendLine('<![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Info>')
    call cursor(s:current - 1, 0)
    call EchoHighlight(s:tips)
endfunction

function InsertInter()
    let s:current = line(".")
    call AppendFirst('<Intervention By-Date = "yyyy-mm-dd"><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Intervention>')
    call cursor(s:current - 2, 26)
endfunction

function InsertJournal()
    " Journal can only starts from top
    let s:current = line(".") 
    call AppendFirst('<?xml version ="1.0" encoding="iso-8859-1"?>')
    call AppendLine('<!DOCTYPE Journal SYSTEM "http://mail.arrayinc.com/kr/v4/ArrAy.dtd">')
    call AppendLine('<?xml-stylesheet type="text/xsl" href="http://mail.arrayinc.com/kr/v4/ArrAy.xsl"?>')
    call AppendLine('<Journal>')
    call AppendLine('<!-- (Header , Entry* , Closure* )-->')
    call AppendLine('<Header')
    call AppendLine('  Client = "ArrAy"')
    call AppendLine('  TeamPriority = "Medium"')
    call AppendLine('  TaskID = ""')
    call AppendLine('  DueDate = "' . strftime("%Y-%m-%d") . '"')
    call AppendLine('  ClientPriority = ""')
    call AppendLine('  CreationDate = "' . strftime("%Y-%m-%d") . '"')
    call AppendLine('  Category = "Design"')
    call AppendLine('  Complexity = "Medium"')
    call AppendLine('  TimeSpent = "0:00"')
    call AppendLine('  HKeyWords = "">')
    call AppendLine('<!-- (Description , Custom*, DispatchEvent (DispatchEvent | CloseEvent)* )-->')
    call AppendLine('<Description><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Description> ')
    call AppendLine('<DispatchEvent')
    call AppendLine('  Authors = "<mauthor>"')
    call AppendLine('  Stage = "Triage"')
    call AppendLine('  State = "Suspended"')
    call AppendLine('  EventTimeStamp = "<mtime>">')
    call AppendLine('</DispatchEvent>')
    call AppendLine('</Header>')
    call AppendLine('<!--=========================================================================-->')
    call AppendLine('</Journal>')

    call cursor(s:current - 10, 0)
    call EchoHighlight(s:tips)
    set filetype=xml

endfunction

function InsertNext()
    let s:current = line(".")
    call AppendFirst('<NextSteps>')
    call AppendLine('<!-- List of the next steps in working this task -->')
    call AppendLine('<![CDATA[')
    call AppendLine('')
    call AppendLine(']]></NextSteps>')
    call cursor(s:current - 1, 0)
endfunction

function InsertRepro()
    let s:current = line(".")
    call AppendFirst('<Repro><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Repro>')
    call cursor(s:current - 1, 0)
endfunction

function InsertResult()
    let s:current = line(".")
    call AppendFirst('<Result StratRef = ""><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Result>')
    call cursor(s:current - 2, 21)
endfunction

function InsertStrat()
    let s:current = line(".")
    call AppendFirst('<Strat ID="' . GetNextID("Strat", "ID", "S") . '" HypoRef="">')
    call AppendLine('<!-- Detailed strategy to prove or disprove the referenced Hypo -->')
    call AppendLine('<![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Strat>')
    call cursor(s:current - 4, 25)
endfunction

function InsertSummary()
    let s:current = line(".")
    call AppendFirst('<Summary>')
    call AppendLine('<!-- Summary of the current entry -->')
    call AppendLine('<![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Summary>')
    call cursor(s:current - 1, 0)
endfunction

function InsertTime()
    "execute "normal i" . Now()
    let c = col(".")
    if c < 1
        let c = 1
    endif
    let str = getline(".")
    let str = strpart(str, 0, c - 1) . Now() . strpart(str, c - 1)
    call setline(".", str)
endfunction

function InsertTimespent()

    let total = 0
    let tsRow = 0 " TimeSpent tag line number
    let i = 1
    let endline = line("$")
    while i <= endline
        let s = getline(i)

        " find TimeSpent tag
        if tsRow == 0 && stridx(s, "<Header") >= 0
            let i = i + 1
            while i <= endline 
                let s = getline(i)
                if stridx(s, "TimeSpent") > -1
                    let tsRow = i
                    break
                endif
                let i = i + 1
            endwhile
        endif

        " find Entries
        if stridx(s, "<Entry") > -1
            let i = i + 1
            let startTime = ""
            let endTime   = ""
            while i <= endline
                let s = getline(i)

                if startTime == "" && stridx(s, "StartTimeStamp") > -1
                    let startTime = GetAttributeValue(s, "StartTimeStamp")
                    let i = i + 1
                    continue
                endif
                if endTime == "" && stridx(s, "EndTimeStamp") > -1
                    let endTime = GetAttributeValue(s, "EndTimeStamp") 
                    let i = i + 1
                    continue
                endif

                if stridx(s, "</Entry>") > -1
                    " roughly check if the time format is correct
                    if strlen(startTime) == 19 && strlen(endTime) == 19
                        let total = total + DateDiff(endTime, startTime)
                    endif
                    break
                endif 
                let i = i + 1
            endwhile
        endif

        let i = i + 1
    endwhile

    if tsRow > 0
        " hh:mm
        let hh = total / 3600
        let mm = (total - hh * 3600) / 60
        let val = "" . hh . ":" . PadLeft(mm, 2)
        let ts = SetAttributeValue(getline(tsRow), "TimeSpent", val)
        call setline(tsRow, ts)
    endif

endfunction

" set all <mendtime> to current time
function InsertEndtime()
    let s:lineNum = 1
    while s:lineNum <= line("$")
        let s = getline(s:lineNum)
        if stridx(s, "<mendtime>") > -1
            let s = substitute(s, "<mendtime>", Now(), "")  
            call setline(s:lineNum, s)
        endif
        let s:lineNum = s:lineNum + 1
    endwhile

endfunction

function InsertText()
    let s:current = line(".")
    call AppendFirst('<Text><![CDATA[')
    call AppendLine('')
    call AppendLine(']]></Text>')
    call cursor(s:current - 1, 0)
endfunction

function InsertWrap()
    let s:current = line(".")
    call AppendFirst('<![CDATA[')
    call AppendLine('')
    call AppendLine(']]>')
    call cursor(s:current - 1, 0)
endfunction

function Trim(str)
    " strip left
    let i = 0
    while i < strlen(a:str)
        if a:str[i] == "\t" || a:str[i] == " " || a:str[i] == "\r" || a:str[i] == "\n"
            let i = i + 1 
        else
            break
        endif
    endwhile

    " strip right
    let j = strlen(a:str) - 1 
    while j > i
        if a:str[j] == "\t" || a:str[j] == " " || a:str[j] == "\r" || a:str[j] == "\n"
            let j = j - 1
        else
            break
        endif
    endwhile

    " At least one char, otherwise is empty
    if j >= i
        return strpart(a:str, i, j - i + 1)
    endif

    return ""

endfunction

" Catch Ctrl+Enter under GUI
function CtrlEnter()
    let str = Trim(getline("."))
    if str == ""
        return ""
    endif

    
    if stridx(s:macros, str) > -1 && strlen(str) > 4
        let s:cmd = "call Insert" . toupper(str[1]) . strpart(str, 2) . "()"
        call setline(".", "")
        exec s:cmd 
    elseif str == "mendtime"
        call setline(".", "")
        call InsertEndtime()
    elseif str == "mtimespent"
        call setline(".", "")
        call InsertTimespent()
    elseif stridx(str, "mtime") > -1 && stridx(str, "mtimespent") == -1
        let str = substitute(getline("."), "mtime", Now(), "")
        call setline(".", str)
    elseif stridx(str, "mdate") > -1
        let str = substitute(getline("."), "mdate", Date(), "")
        call setline(".", str)
    endif

    if !s:insert_mode
        " <ESC> will cause the cursor to move back 1 char.
        call cursor(line("."), col(".") + 1)
        return "\<ESC>"
    endif

    return ""
endfunction

" In compatible with version < 7.0
" there's no "for" statement 
function AddToList(listName, ops, exclude)
    let i = 1
    let len = strlen(a:ops)
    let item = ""
    " discard the first ':'
    while i < len 
        if a:ops[i] != ":" 
            let item = item . a:ops[i]
        else
            " find one
            if item != a:exclude
                call add(a:listName, item)
            endif
            let item = ""
        endif
        let i = i + 1
    endwhile 

endfunction

function GetDroplist(attrName, attrValue)

    let items = []
    let ops = ""
    
    if a:attrName == "Category"
        let ops = s:journalCategoris
    elseif a:attrName == "Complexity"
        let ops = s:journalComplexities
    elseif a:attrName == "TeamPriority"
        let ops = s:journalTeamPriorities
    elseif a:attrName == "ClientPriority"
        let ops = s:journalTeamPriorities
    elseif a:attrName == "ClosedReason"
        let ops = s:closeReasons
    elseif a:attrName == "State"
        let ops = s:states
    elseif a:attrName == "Stage"
        let ops = s:stages
    elseif a:attrName == "Purpose"
        let ops = s:purposes
    elseif a:attrName == "InfoType"
        let ops = s:infoTypes
    endif
    
    if ops != ""
        if stridx(ops, a:attrValue) > -1
            call add(items, a:attrValue)        
        endif

        " VIM 7 statement
        "for i in split(ops, ":")
        "    if i != "" && i != a:attrValue
        "        call add(items, i)
        "    endif
        "endfor 

        call AddToList(items, ops, a:attrValue)
    endif

    return items

endfunction

let s:currentLine = ""
let s:currentCol  = 0
let s:currrentRow = 0
let s:currentAttr = ""
let s:currentVal  = ""

function OmniHandler(findstart, base)
   
   "
   " Vim 7 Bug????
   " getline("."), col(".") and line(".") returns not what I want!
   "

   if a:findstart
       " return the start position 
       return s:currentCol 
   else
       return GetDroplist(s:currentAttr, s:currentVal)
   endif
endfunction

function PopupMenu()
       
    set omnifunc=OmniHandler
    return "\<C-X>\<C-O>\<Down>\<Up>"

endfunction

"
" Only works under InsertMode with version > 700
" because Omni can only works under InsertMode. 
"
function OnPressF2()

    let back = ""
    if g:originalMode == g:NORMAL_MODE
        let back = "\<Esc>\<Right>"
    endif

    if !IsWAD()
        return back . "\<F2>"
    endif

    if !IsCorrectPos(getline("."), col(".")) 
        call EchoError("Please place cursor within the double quotations!")
        return back
    endif

    let s:currentLine = getline(".")
    let s:currentCol  = col(".")
    let s:currentRow  = line(".")
    let s:currentAttr = GetAttributeName(s:currentLine, s:currentCol)
    let s:currentVal  = GetAttributeValue(s:currentLine, s:currentAttr)

    " We'll update the timestamp directly if there is one
    if s:currentAttr == "EndTimeStamp"
        let update = SetAttributeValue(s:currentLine, s:currentAttr, Now())
        call setline(".", update)
        return back
    endif 

    " check if current Attribute has droplist
    if stridx(s:droplist, s:currentAttr . ",") < 0
        return back
    endif

    let new  = SetAttributeValue(s:currentLine, s:currentAttr, "")
    call setline(".", new)

    " search backward before the last '"' 
    let lPos  = s:currentCol - 2
    while lPos > 0
        if s:currentLine[lPos] == '"'
            break
        endif
        let lPos = lPos - 1
    endwhile
    let s:currentCol = lPos + 2
    call cursor(0, lPos + 2)            

    return PopupMenu()

endfunction

function PressKey(key, curMode)

	 let menuVisible = pumvisible()

	 if a:key == "F2" 
		  if menuVisible
				return "\<Down>"
		  endif
		  " Begin to activate the popu menu, store current mode
		  let g:originalMode = a:curMode
		  return OnPressF2()
	 endif

	 if a:key == "Enter" 
		  if menuVisible && g:originalMode == g:NORMAL_MODE
				return "\<Enter>\<Esc>"
		  else
				return "\<Enter>"
		  endif
	 endif

	 if a:key == "j" &&  menuVisible
		  return "\<Down>"	 
	 endif

	 if a:key == "k" && menuVisible
		  return "\<Up>"
	 endif

	 if menuVisible && (a:key == "h" || a:key == "l")
		  return ""
	 endif

	return a:key
endfunction


""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"
" Key Mappings
"
" - Please don't insert extra spaces or tabs after "<silent><F2>"
" - Please don't insert extra spaces after the last "<CR>"
"
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

if v:version >= 700
   map<silent><F2> i<C-R>=PressKey("F2", g:NORMAL_MODE)<CR>
   imap<silent><Enter> <C-R>=PressKey("Enter", g:INSERT_MODE)<CR>
   imap<silent><F2> <C-R>=PressKey("F2", g:INSERT_MODE)<CR>
	imap<silent>j <C-R>=PressKey("j", g:INSERT_MODE)<CR>
   imap<silent>k <C-R>=PressKey("k", g:INSERT_MODE)<CR>
   imap<silent>h <C-R>=PressKey("h", g:INSERT_MODE)<CR>
   imap<silent>l <C-R>=PressKey("l", g:INSERT_MODE)<CR>
else
   imap<silent><F2> <C-R>=ScrollOption()<CR>
   map<silent><F2> :call ScrollOption()<CR>
endif

if has("gui_running")
    imap <silent>  <C-Enter> <C-R>=CtrlEnter()<CR>
endif

if s:insert_mode 
    map <silent> mauditor   :call InsertAuditor()<CR>i
    map <silent> mclose     :call InsertClose()<CR>i
    map <silent> mclosure   :call InsertClosure()<CR>i
    map <silent> mcode      :call InsertCode()<CR>i
    map <silent> mcrew      :call InsertCrew()<CR>i
    map <silent> mcustom    :call InsertCustom()<CR>i
    map <silent> mdate      :call InsertDate()<CR>i
    map <silent> mdisp      :call InsertDisp()<CR>i
    map <silent> memail     :call InsertEmail()<CR>i
    map <silent> mendtime   :call InsertEndtime()<CR>i
    map <silent> mentry     :call InsertEntry()<CR>i
    map <silent> mfile      :call InsertFile()<CR>i
    map <silent> mhypo      :call InsertHypo()<CR>i
    map <silent> minfo      :call InsertInfo()<CR>i
    map <silent> minter     :call InsertInter()<CR>i
    map <silent> mjournal   :call InsertJournal()<CR>i
    map <silent> mnext      :call InsertNext()<CR>i
    map <silent> mrepro     :call InsertRepro()<CR>i
    map <silent> mresult    :call InsertResult()<CR>i
    map <silent> mstrat     :call InsertStrat()<CR>i
    map <silent> msummary   :call InsertSummary()<CR>i
    map <silent> mtime      :call InsertTime()<CR>i
    map <silent> mtimespent :call InsertTimespent()<CR>i
    map <silent> mtext      :call InsertText()<CR>i
    map <silent> mwrap      :call InsertWrap()<CR>i
else
    map <silent> mauditor   :call InsertAuditor()<CR>
    map <silent> mclose     :call InsertClose()<CR>
    map <silent> mclosure   :call InsertClosure()<CR>
    map <silent> mcode      :call InsertCode()<CR>
    map <silent> mcrew      :call InsertCrew()<CR>
    map <silent> mcustom    :call InsertCustom()<CR>
    map <silent> mdate      :call InsertDate()<CR>
    map <silent> mdisp      :call InsertDisp()<CR>
    map <silent> memail     :call InsertEmail()<CR>
    map <silent> mendtime   :call InsertEndtime()<CR>
    map <silent> mentry     :call InsertEntry()<CR>
    map <silent> mfile      :call InsertFile()<CR>
    map <silent> mhypo      :call InsertHypo()<CR>
    map <silent> minfo      :call InsertInfo()<CR>
    map <silent> minter     :call InsertInter()<CR>
    map <silent> mjournal   :call InsertJournal()<CR>
    map <silent> mnext      :call InsertNext()<CR>
    map <silent> mrepro     :call InsertRepro()<CR>
    map <silent> mresult    :call InsertResult()<CR>
    map <silent> mstrat     :call InsertStrat()<CR>
    map <silent> msummary   :call InsertSummary()<CR>
    map <silent> mtime      :call InsertTime()<CR>
    map <silent> mtimespent :call InsertTimespent()<CR>
    map <silent> mtext      :call InsertText()<CR>
    map <silent> mwrap      :call InsertWrap()<CR>
endif

" EOF
" ----------------------------------------------------------------------------- 
